// Functions associated with the Mesh class
//-----------------

//-----------------
// Includes libraries and other files
#include "Mesh.h"
//-----------------

//-----------------
//// CONSTRUCTOR - Mesh
Mesh::Mesh()
	: device_( nullptr), mesh_( nullptr), mesh_materials_( nullptr), mesh_textures_( nullptr), dw_num_materials_( 0)
{}

//// CONSTRUCTOR - Mesh
Mesh::Mesh( const Mesh& m) {
	// If the parameter is an empty class, then attempting to load its mesh will cause
	// and assertion error in Mesh::load_mesh; so initialise all the values instead.
	// Unfortunately, calling Mesh() doesn't actually reset the values, so I had to write it all out again.
	if( m.device_ == nullptr) {
		device_ = nullptr;
		mesh_ = nullptr;
		mesh_materials_ = nullptr;
		mesh_textures_ = nullptr;
		dw_num_materials_ = 0;
	}
	else
		load_mesh( m.device_, m.get_file_name().c_str());
}

//// DESTRUCTOR - ~Mesh
Mesh::~Mesh() {
	unload_mesh();
}
//-----------------

//-----------------
//// FUNCTION - render
// render the mesh's geometry, textures and materials on the given device
void Mesh::render() const {
	// Preconditions :- requires a D3DDevice device
	assert( device_ != nullptr);

	// draw the mesh in sections - according to the different textures and materials
	for( DWORD i( 0); i < dw_num_materials_; ++i) {
		// activate material and texture for that section
		device_->SetMaterial( &( mesh_materials_[i]));
		device_->SetTexture( 0, mesh_textures_[i]);
		// render the section that uses this material and texture
		mesh_->DrawSubset( i);
	}
}

//// FUNCTION - render
// render the mesh's geometry, textures and materials on the given device at a specific location with a specific size and rotation
void Mesh::render( float tx, float ty, float tz, float sx, float sy, float sz, float ry) const {
	// Precondition :- requires a D3DDevce device
	assert( device_ != nullptr);

	// translate the object using parameters
	D3DXMATRIX translate_mat, scale_mat, rotY_mat;
	D3DXMatrixIdentity( &translate_mat);
	D3DXMatrixIdentity( &scale_mat);
	D3DXMatrixIdentity( &rotY_mat);

	D3DXMatrixTranslation(	&translate_mat,	tx, ty, tz);
	D3DXMatrixScaling(		&scale_mat,		sx, sy, sz);
	D3DXMatrixRotationY(	&rotY_mat,		D3DXToRadian( ry));

	D3DXMatrixMultiply(		&translate_mat, &scale_mat, &translate_mat);
	D3DXMatrixMultiply(		&translate_mat, &rotY_mat, &translate_mat);
	device_->SetTransform( D3DTS_WORLD, &translate_mat);

	// draw the mesh in sections - according to the different textures and materials
	render();
}

//// FUNCTION - render
// render the mesh's geometry, textures and materials on the given device at a specific location with a specific size
void Mesh::render( float tx, float ty, float tz, float sx, float sy, float sz) const {
	// Precondition :- requires a D3DDevce device
	assert( device_ != nullptr);

	// translate the object using parameters
	D3DXMATRIX translate_mat, scale_mat;
	D3DXMatrixIdentity( &translate_mat);
	D3DXMatrixIdentity( &scale_mat);

	D3DXMatrixTranslation(	&translate_mat,	tx, ty, tz);
	D3DXMatrixScaling(		&scale_mat,		sx, sy, sz);
	D3DXMatrixMultiply(		&translate_mat, &scale_mat, &translate_mat);
	device_->SetTransform( D3DTS_WORLD, &translate_mat);

	// draw the mesh in sections - according to the different textures and materials
	render();
}

//// FUNCTION - render
// render the mesh's geometry, textures and materials on the given device at a specific location
void Mesh::render( float x, float y, float z) const {
	// Precondition :- requires a D3DDevce device
	assert( device_ != nullptr);

	// translate the object using parameters
	D3DXMATRIX translate_mat;

	D3DXMatrixIdentity( &translate_mat);
	D3DXMatrixTranslation( &translate_mat, x, y, z);
	device_->SetTransform( D3DTS_WORLD, &translate_mat);

	// draw the mesh in sections - according to the different textures and materials
	render();
}

//// FUNCTION - render
// renders a specific subset of the mesh's geometry, texture and material on the given device
void Mesh::render( DWORD sub_mesh_number) const {
	// Precondition :- requires a D3DDevice device and a positive subset number
	assert( device_ != nullptr && sub_mesh_number >= 0);

	mesh_->DrawSubset( sub_mesh_number);
}

//// FUNCTION - render_opacity
// renders an object with the specified opacity
void Mesh::render_opacity( const int& o) const {
	// Precondition :- requires a D3DDevce device
	assert( device_ != nullptr);

	DWORD AlphaValue;
	AlphaValue = D3DCOLOR_ARGB( o, 255, 255, 255);

	device_->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE);
	device_->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
	device_->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
	device_->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
	device_->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);

	device_->SetTextureStageState( 0, D3DTSS_CONSTANT, AlphaValue);
	device_->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_CONSTANT);
	device_->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE);
	device_->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);

	render();
}

//// FUNCTION - get_file_name
// return the name of the .x file that this mesh has loaded in
std::string Mesh::get_file_name() const {
	// Pre-conditions :- none
	assert( true);

	return file_name_;
}

//// FUNCTION - get_mesh
// return the instance's mesh
LPD3DXMESH Mesh::get_mesh() const {
	// Pre-conditions :- none
	assert( true);

	return mesh_;
}

//// FUNCTION - load_mesh
// load a mesh into this object using specified D3D device
HRESULT Mesh::load_mesh( LPDIRECT3DDEVICE9 device, LPCTSTR file_name) {
	// Preconditions :- the D3DDevice must contain something
	assert( device != nullptr);

	// save the file name
	file_name_ = file_name;

	// add the file path to the file name
	// for some unknown reason the line "file_name = (PATH_MODELS + file_name).c_str();" doesn't work
	std::string s( PATH_MODELS + file_name);
	file_name = s.c_str();

	// set a device for this object
	device_ = device;

	// local variable to temporarily hold data extracted from the .x file
	LPD3DXBUFFER pD3DXMtrlBuffer;

	// load the mesh from the specified file
	if( FAILED( D3DXLoadMeshFromX( file_name, D3DXMESH_SYSTEMMEM, device_, NULL, &pD3DXMtrlBuffer, NULL, &dw_num_materials_, &mesh_))) {
		MessageBox( NULL, "Error Code: 001", "Mesh::load_mesh", MB_OK);
		return E_FAIL;
	}

	// extract the material properties and texture names from the pD3DXMtrlBuffer
	D3DXMATERIAL *d3dxMaterials = ( D3DXMATERIAL*)pD3DXMtrlBuffer->GetBufferPointer();
	// create array to hold all the new textures and materials in memory.
	mesh_materials_ = new D3DMATERIAL9[dw_num_materials_];
	mesh_textures_ = new LPDIRECT3DTEXTURE9[dw_num_materials_];

	// for each sub-mesh
	for( DWORD i( 0); i < dw_num_materials_; ++i) {
		// copy the material
		mesh_materials_[i] = d3dxMaterials[i].MatD3D;

		// set the ambient colour for the material (D3DX does not do this)
		mesh_materials_[i].Ambient = mesh_materials_[i].Diffuse;

		// if there is a file name for the texture for that part of the mesh
		mesh_textures_[i] = nullptr;
		if (d3dxMaterials[i].pTextureFilename != NULL && lstrlen( d3dxMaterials[i].pTextureFilename) > 0)
			// create the texture
			if( FAILED( D3DXCreateTextureFromFile( device_, d3dxMaterials[i].pTextureFilename, &mesh_textures_[i])))
				MessageBox( NULL, "Error Code: 002", "load_mesh", MB_OK);
	}

	// done with the material buffer
	pD3DXMtrlBuffer->Release();

	return S_OK;
}

//// FUNCTION - material
// set the material to be used
void Mesh::material( int i) {
	// Preconditions :- parameter must be a positive number and the device must exist
	assert( i >= 0 && device_ != nullptr);

	device_->SetMaterial( &( mesh_materials_[i]));
}

//// FUNCTION - texture
// set the texture to be used
void Mesh::texture( int i) {
	// Preconditions :- parameter must be a positive number and the device must exist
	assert( i >= 0 && device_ != nullptr);

	device_->SetTexture( 0, mesh_textures_[i]);
}

//// FUNCTION - set_colour
// set the colour for the object
void Mesh::set_colour( float r, float g, float b) {
	// Pre-conditions :- the device must exist
	assert( device_ != nullptr);

	D3DMATERIAL9 m;

	// Define a material
 	ZeroMemory( &m, sizeof( D3DMATERIAL9));
	m.Diffuse.r = r;
	m.Diffuse.g = g;
	m.Diffuse.b = b;
	device_->SetMaterial( &m);
}

//// OVERLOADED OPERATOR - ==
bool Mesh::operator ==( const Mesh& m) const {
	// Pre-conditions :- none
	assert( true);

	if( get_mesh() == m.get_mesh())
		return true;
	else
		return false;
}

//// OVERLOADED OPERATOR - ==
bool Mesh::operator ==( const std::string& s) const {
	// Pre-conditions :- none
	assert( true);

	if( get_file_name() == s)
		return true;
	else
		return false;
}

//// OVERLOADED OPERATOR - =
Mesh& Mesh::operator =( const Mesh& m) {
	// Pre-conditions :- none
	assert( true);

	// ensure that no self-assignment takes place
	if( this != &m)
		if( m.device_ != nullptr)
			(*this).load_mesh( m.device_, m.get_file_name().c_str());
		else
			unload_mesh();

	return (*this);
}

//// FUNCTION - unload_mesh
// delete all the memory that's using the heap and reset the pointers
void Mesh::unload_mesh() {
	// delete all the materials then the array itself
	if( mesh_materials_ != nullptr) {
		delete[] mesh_materials_;
		mesh_materials_ = nullptr;
	}

	// delete all the textures...
	if( mesh_textures_ != nullptr) {
		// ... for each sub-mesh...
		for( DWORD i( 0); i < dw_num_materials_; ++i)
			if( mesh_textures_[i])
				mesh_textures_[i]->Release();

		// ... then the array itself
		delete[] mesh_textures_;
		mesh_textures_ = nullptr;
	}

	// release the mesh
	if( mesh_ != nullptr) {
		mesh_->Release();
		mesh_ = nullptr;
	}
}